local U = require "packages/utility/utility"

---@class LM : Object
local LM = {}

---通用函数存放地（如典中典之阶段技）

-- 阶段技（出牌阶段限一次……）的canUse
---@param self ActiveSkill | ViewAsSkill
---@param player Player
LM.phaseCanUse = function(self, player)
  return player:usedSkillTimes(self.name, Player.HistoryPhase) == 0
end

-- 从牌堆（或弃牌堆）内抽任意张牌（优先从上到下）
---@param room Room @ 房间
---@param pattern string @ 查找规则
---@param num? number @ 查找数量
---@param fromPile? CardArea[] @ 查找的来源区域
---@return integer[] @ id列表 可能空
LM.GetCardByRule = function(room, pattern, num, fromPile)
  local exp = Exppattern:Parse(pattern)
  num = num or 1
  local piles = {}
  for _, area in ipairs(fromPile) do
    if area == Card.DrawPile then
      table.insertTable(piles, room.draw_pile)
    elseif area == Card.DiscardPile then
      --- 弃牌堆要特殊处理
      table.insertTable(piles, table.reverse(room.discard_pile))
    elseif area == Card.Processing then
      table.insertTable(piles, room.processing_area)
    elseif area == Card.Void then
      table.insertTable(piles, room.void)
    end
  end

  local cardPack = {}
  for _, cid in ipairs(piles) do
    local card = Fk:getCardById(cid)
    if exp:match(card) then
      table.insert(cardPack, cid)
      if cardPack[num] ~= nil then
        break
      end
    end
  end
  return cardPack
end

---用以获得将领技能赋予方式……害

LM.GetSvtSkill = 1
LM.LoseSvtSkill = 2

---时机表
-- 时机：初始化将领时
LM.initializeServant = "lm.initializeServant"
-- 时机：获得将领前
LM.importServants = "lm.importServants"
-- 时机：获得将领时
LM.importingServants = "lm.importingServants"
-- 时机：获得将领后
LM.importedServants = "lm.importedServants"
-- 时机：移除将领时
LM.servantsRemoving = "lm.servantsRemoving"
-- 时机：移除将领后
LM.servantsRemoved = "lm.servantsRemoved"
-- 时机：移除将领结束后
LM.afterServantsRemoved = "lm.afterServantsRemoved"
-- 时机：索引将领时
LM.indexingServants = "lm.indexingServants"
-- 时机：发现将领前
LM.findingServant = "lm.findingServant"
-- 时机：发现将领时
LM.findServant = "lm.findServant"

-- 将领数组
---@class ServantListStruct
---@field public lordId integer @ 标记主人ID
---@field public max integer @ 将领上限
---@field public svts ServantStruct[] @ 将领数组

-- 将领信息
---@class ServantStruct
---@field public name string             @ 将领名
---@field public back string             @ 将领背面
---@field public hide boolean            @ 是否暗置<曹丕>
---@field public mark table<string, any> @ 将领标记数组

-- 获得一个角色的军力技（有就是主公）
---@param player Player
---@return Skill?
LM.getLordSkill = function(player)
  return table.find(player.player_skills, function(s)
    return s.lmLordList
  end)
end

-- 获得一个将领的军力技（有就是主公）
---@param general string
---@return Skill?
LM.getLordGenenalSkill = function(general)
  local actual = Fk.generals[general]
  if not actual then return end
  return table.find(actual.skills, function(s)
    return s.lmLordList
  end)
end

-- 获得主公将领的所有将领技能
---@param player Player
---@return Skill[]
LM.getSvtsSkillsByLord = function(player)
  if not LM.getLordSkill(player) then return end
  local getsvts = LM.getServants(player)
  if #getsvts == 0 then return end

  local getskills = {}
  for _,st in ipairs(getsvts) do
    table.insertTable(getskills, LM.getServantSkills(st))
  end
  return getskills
end

-- 设置军力技提供的将领
---@param lord Skill 军力技
---@param svt string 将领ID
LM.setlordServant = function(lord,svt)
  if not lord.lmLordList then lord.lmLordList = {} end
  table.insertIfNeed(lord.lmLordList, svt)
  -- p(LM.lord_svts[lord])
end

-- 初始化将领
---@param player ServerPlayer @ 将领牌请求者
---@param name string @ 将领名
---@return ServantStruct @ 将领
LM.initServant = function(player, name)
  local svt = { ---@type ServantStruct
    name = name,
    back = LM.getSvtBack(name),
    hide = false,
    mark = {},
  }
  -- 时机：初始化将领时
  player.room.logic:trigger("lm.initializeServant", player, svt, false)

  return svt
end

-- 给一个将领设置标记
---@param player ServerPlayer @ 主公角色
---@param svt string @ 将领名
---@param mark string @ 标记名
---@param value any @ 标记值
LM.setSvtMark = function(player, svt, mark, value)
  local skill = LM.getLordSkill(player)
  if not skill then return end

  local getsvts_mark = "@[lm_servants]" .. skill.name
  local mark_value = LM.getServantsMark(player)
  for _, actual in ipairs(mark_value.svts) do
    if actual.name == svt then
      actual.mark[mark] = value
      break
    end
  end

  player.room:setPlayerMark(player, getsvts_mark, mark_value)
end

-- 给一个将领设置暗置状态
---@param player ServerPlayer @ 主公角色
---@param svt string @ 将领名
---@param value boolean @ 是否暗置
LM.setSvtHide = function(player, svt, value)
  local skill = LM.getLordSkill(player)
  if not skill then return end

  local getsvts_mark = "@[lm_servants]" .. skill.name
  local mark_value = LM.getServantsMark(player)
  for _, actual in ipairs(mark_value.svts) do
    if actual.name == svt then
      actual.hide = value
      break
    end
  end

  player.room:setPlayerMark(player, getsvts_mark, mark_value)
end

-- 确认一个将领的“背面”是什么样的
---@param svt string @ 将领名
---@return string?
LM.getSvtBack = function(svt)
  local actual = Fk.generals[svt]
  if not actual then return end

  if actual.lmSvtBack then return actual.lmSvtBack end

  for name, skill in pairs(Fk.skills) do
    if skill.lmLordList and table.contains(skill.lmLordList, svt) then
      return name .. "-lmback"
    end
  end
end

-- 获得一个将领的技能
---@param servant ServantStruct @ 将领
---@return string[] @ 技能名数组
LM.getServantSkills = function(servant)
  local svtgen = Fk.generals[servant.name]
  local ret = svtgen:getSkillNameList()
  return ret
end

-- 获得一个将领的技能
---@param servant ServantStruct @ 将领
---@return string[] @ 技能名数组
LM.getServantSkillHistory = function(player, servant)
  local svtgen = Fk.generals[servant.name]
  local ret = svtgen:getSkillNameList()
  return ret
end

-- 获得一个将领的尺寸（嗯）
---@param player Player @ 主公角色
---@param servant ServantStruct @ 将领
---@return integer | false @ 将领尺寸，为空则表示不计入将领上限
LM.getServantSize = function(player, servant)
  assert(LM.getLordSkill(player))
  ---@type LM.ServantSkill[]
  local status_skills = Fk:currentRoom().status_skills[LM.ServantSkill] or Util.DummyTable
  local max_fixed, correct = nil, 0
  for _, skill in ipairs(status_skills) do
    if skill:excludeFrom(player, servant) then
      return false
    end

    local f = skill:fixedSize(player, servant)
    if f ~= nil then
      max_fixed = f
      break
    end
    correct = correct + (skill:correctSize(player, servant) or 0)
  end

  return max_fixed or (1 + math.max(correct, 0))
end

-- 检测一个将领是否能被移除
---@param player Player @ 主公角色
---@param servant? ServantStruct @ 检测移除的将领
---@param remover? Player @ 执行人
---@param noback? bool @ 是否不返回将领池
---@return bool
LM.canRemoveServant = function(player, servant, remover, noback)
  assert(LM.getLordSkill(player))
  ---@type LM.ServantSkill[]
  local ServantSkill = Fk:currentRoom().status_skills[LM.ServantSkill] or Util.DummyTable
  for _, svtskill in ipairs(ServantSkill) do
    if svtskill:prohibitRemove(player, servant, remover, noback) then
      return false
    end
  end
  return true
end

-- 返回将领池
---@param player Player @ 主公角色
---@return ServantStruct[]    @ 将领池
LM.getServantsPool = function(player)
  local skill = LM.getLordSkill(player)
  if not skill then return end

  local svts_mark = "@[lm_servants]&" .. skill.name
  return player:getTableMark(svts_mark)
end

-- 设置将领池
---@param player ServerPlayer @ 主公角色
---@param value ServantStruct[] @ 将领池
---@param pos? integer @ 置入位置
---@return bool
LM.setServantsPool = function(player, value, pos)
  if #value == 0 then return end
  local skill = LM.getLordSkill(player)
  if not skill then return end

  local svts_mark = "@[lm_servants]&" .. skill.name
  local pool = LM.getServantsPool(player)
  for i, e in ipairs(value) do
    if pos then
      table.insert(pool, pos + i - 1, e)
    else
      table.insert(pool, math.random(#pool + 1), e)
    end

  end
  player.room:setPlayerMark(player, svts_mark, pool)
  return true
end

-- 从将领池移除一个武将
---@param player ServerPlayer @ 主公角色
---@param index string | integer @ 将领名/移除位置
---@return bool
LM.removeServantFromPool = function(player, index)
  local skill = LM.getLordSkill(player)
  if not skill then return end

  local svts_mark = "@[lm_servants]&" .. skill.name
  local pool = LM.getServantsPool(player)
  if type(index) == "number" then
    table.remove(pool, index)
  elseif type(index) == "string" then
    local pos = nil
    for i, v in ipairs(pool) do
      if v.name == index then
        pos = i
        break
      end
    end
    if pos == nil then return false end
    table.remove(pool, pos)
  else
    return false
  end

  player.room:setPlayerMark(player, svts_mark, pool)
  return true
end

-- 返回现有将领“标记”，没有则初始化一个
---@param player Player @ 主公角色
---@return ServantListStruct @ 格式化将领位
LM.getServantsMark = function(player)
  local skill = LM.getLordSkill(player)
  if not skill then return end

  local getsvts_mark = "@[lm_servants]" .. skill.name
  local value = type(player:getMark(getsvts_mark)) == "table" and player:getMark(getsvts_mark) or {} ---@type ServantListStruct
  if not value.max then value.max = 0 end
  if not value.lordId then value.lordId = player.id end
  if not value.svts then value.svts = {} end
  return value
end

-- 返回现有将领
---@param player Player @ 主公角色
---@return ServantStruct[]   @ 将领角色
LM.getServants = function(player)
  local value = LM.getServantsMark(player) or {}
  return table.simpleClone(value.svts or {})
end

-- 按照规则返回现有将领
---@param player Player @ 主公角色
---@param func fun(svt: ServantStruct, index?: number, svts?: ServantListStruct): bool @ 筛选条件
---@return ServantStruct[]   @ 将领角色
LM.getServantsByRule = function(player, func)
  local value = LM.getServants(player)
  return table.filter(value, function(e, i)
    return func(e, i, value)
  end)
end

-- 按名字返回将领
---@param player Player @ 主公角色
---@param name string @ 将领名
---@param inpool? bool @ 是否改为在将领池内寻找
---@return ServantStruct? @ 将领
LM.getServant = function(player, name, inpool)
  if not inpool then
    return table.find(LM.getServants(player), function(s) return s.name == name end)
  else
    return table.find(LM.getServantsPool(player), function(s) return s.name == name end)
  end
end

-- 是否能获得某个将领？（必须不在将领内）
---@param player Player @ 主公角色
---@param name string @ 将领名
---@param no_pool? bool @ 是否不考虑将领池？
---@return bool
LM.canFindServant = function(player, name, no_pool)
  local ret = table.find(LM.getServants(player), function(s) return s.name == name end)
  if not ret then
    if no_pool or table.find(LM.getServantsPool(player), function(s) return s.name == name end) then return true end
  end
  return false
end

-- 按技能返回将领
---@param player Player @ 主公角色
---@param skillName string @ 技能名
---@param includepool? bool @ 是否也在将领池内寻找
---@return ServantStruct? @ 将领
LM.getServantFromSkill = function(player, skillName, includepool)
  assert(LM.getLordSkill(player))

  local findskill = function(svt)
    return table.contains(LM.getServantSkills(svt), skillName)
  end
  local ret = table.find(LM.getServants(player), findskill)
  if ret == nil and includepool then
    ret = table.find(LM.getServants(player), findskill)
  end
  return ret
end

-- 按技能返回一个池内的“此将领”
---@param skillName string @ 技能名
---@param pool? ServantStruct[] @ 将领池
---@return ServantStruct[] @ 将领，可能为空
LM.getServantsFromSkill = function(skillName, pool)
  return table.filter(pool, function(svt)
    return table.contains(LM.getServantSkills(svt), skillName)
  end)
end

-- 返回一个角色
---@param player Player @ 主公角色
---@param skillName string @ 技能名
---@param pool? ServantStruct[] @ 将领池
---@return ServantStruct[] @ 将领，可能为空
LM.getServantSkillHistory = function(player, skillName, pool)
  return table.filter(pool, function(svt)
    return table.contains(LM.getServantSkills(svt), skillName)
  end)
end

LM.LMImportServants = "GameEvent.LMImportServants"

-- LMImportStruct 获得将领的数据
---@class LMImportStruct
---@field public who ServerPlayer @ 被执行人
---@field public svts ServantStruct[] @ 将领群
---@field public proposer? ServerPlayer @ 执行人
---@field public pos? integer @ 置入位置
---@field public skillName? string @ 技能名

Fk:addGameEvent(LM.LMImportServants, nil, function(self)
  local data = table.unpack(self.data) ---@type LMImportStruct
  local room = self.room ---@class Room
  local player = data.who
  local logic = room.logic
  local skill = LM.getLordSkill(player)
  assert(skill)

  local getsvts_mark = "@[lm_servants]" .. skill.name
  local mark = LM.getServantsMark(player)

  -- 时机：获得将领前
  logic:trigger("lm.importServants", player, data, false)

  -- 时机：获得将领时
  logic:trigger("lm.importingServants", player, data, false)

  data.svts = table.slice(data.svts, 1, LM.getServantSlot(player) + 1)
  if #data.svts == 0 then return false end

  if not data.pos then data.pos = #mark.svts + 1 end
  local inserted = {}
  for i, e in ipairs(data.svts) do
    table.insert(inserted, e)
    table.insert(mark.svts, data.pos + i - 1, e)
  end

  room:setPlayerMark(player, getsvts_mark, mark)
  LM.handleSvtSkills(player, data.svts, LM.GetSvtSkill)

  if #inserted ~= 0 then
    LM.sendLog(room, {
      type = "#lm_importServants",
      from = player.id,
      arg = #inserted,
      svts = inserted
    })
  end

  -- 时机：获得将领后
  logic:trigger("lm.importedServants", player, data, false)
  return true
end)

LM.LMRemoveServants = "GameEvent.LMRemoveServants"

-- LMRemoveStruct 移除将领的数据
---@class LMRemoveStruct
---@field public who ServerPlayer @ 被执行人
---@field public svtnames string[] @ 移除将领所用索引
---@field public proposer? ServerPlayer @ 执行人
---@field public noback? bool @ 是否不返回将领池
---@field public skillName? string @ 技能名
---@field public svts? ServantStruct[] @ 被移除的将领

Fk:addGameEvent(LM.LMRemoveServants, nil, function(self)
  local data = table.unpack(self.data) ---@type LMRemoveStruct
  local room = self.room ---@class Room
  local player = data.who
  local logic = room.logic
  local skill = LM.getLordSkill(player)
  assert(skill)

  local getsvts_mark = LM.getServantsMark(player)
  local getsvts = getsvts_mark.svts
  if #getsvts == 0 then return end

  data.svts = {}
  for _,st in ipairs(data.svtnames) do
    local svt = table.find(getsvts, function(s) return s.name == st end)
    if svt and LM.canRemoveServant(player, svt, data.proposer, data.noback) then
      table.insert(data.svts, svt)
    end
  end

  if #data.svts == 0 then
    return false
  end

  -- 时机：移除将领时
  logic:trigger("lm.servantsRemoving", player, data, false)

  data.svts = table.filter(data.svts, function(svt)
    return LM.canRemoveServant(player, svt, data.proposer, data.noback) and
    table.contains(getsvts, svt)
  end)

  if #data.svts == 0 then
    return false
  end

  local removed = {}
  for _, svt in ipairs(data.svts) do
    if table.removeOne(getsvts, svt) then
      table.insert(removed, svt)
    end
  end
  data.svts = removed

  room:setPlayerMark(player, "@[lm_servants]" .. skill.name, getsvts_mark)

  LM.sendLog(room, {
    type = "#lm_removeServants",
    from = player.id,
    arg = #removed,
    svts = removed
  })

  -- 时机：移除将领后
  logic:trigger("lm.servantsRemoved", player, data, false)
  LM.handleSvtSkills(player, data.svts, LM.LoseSvtSkill)
end, nil, function(self)
  local data = table.unpack(self.data) ---@type LMRemoveStruct
  local room = self.room ---@class Room
  local player = data.who
  local logic = room.logic

  -- 时机：移除将领结束后
  logic:trigger("lm.afterServantsRemoved", player, data, false)

  if not data.noback then
    LM.setServantsPool(player, data.svts)
  end
end)

-- LMLogMessage 自定义log，LM.sendLog专用，需要提前翻译并删去所有相关log
---@class LMLogMessage: LogMessage
---@field svts? ServantStruct[] @ 要替换%svts的将领，先得翻译一把

-- 向多名玩家发送log。
---@param room Room @ 顾名思义，就是room
---@param logdata LMLogMessage @ 广播消息的数据
---@param players? ServerPlayer[] @ 要告知的玩家列表，默认为所有人
LM.sendLog = function(room, logdata, players)
  players = players or room.players
  for _, p in ipairs(players) do
    ---@type LogMessage
    local log = table.simpleClone(logdata)
    log.svts = nil

    local source = room:getPlayerById(logdata.from)
    local text = Fk:translate(log.type)
    if logdata.svts then
      local svts = LM.parseServants(logdata.svts, source, p)
      text = string.gsub(text, "%%svts", svts)
    end

    log.type = text
    p:doNotify("GameLog", json.encode(log))
  end
end

-- 翻译将领名数组desu
---@param svts ServantStruct[] @ 将领名数组
---@param user? ServerPlayer @ 将领的主人，若不存在，则默认全局可见
---@param viewer? ServerPlayer @ 观察者，默认为将领主人
---@return string @ 将领名字符串
LM.parseServants = function(svts, user, viewer)
  local txts = {}
  local all_hidden = true
  local hide_num = 0
  for _, svt in ipairs(svts) do
    if user and user ~= viewer and svt.hide then
      hide_num = hide_num + 1
    else
      all_hidden = false
      local ret = string.format('<font color="#0598BC"><b>%s</b></font>', Fk:translate(svt.name))
      table.insert(txts, ret)
    end
  end

  if all_hidden then return "" end
  if hide_num > 0 then
    local suffix = hide_num > 1 and ("x" .. tostring(hide_num)) or ""
    table.insert(txts, Fk:translate("lm_ishide") .. suffix)
  end
  return table.concat(txts, ", ")
end

Fk:loadTranslationTable{
  ["lm_ishide"] = "暗置将领",
  ["#lm_importServants"] = "%from 发现了 %arg 名将领 %svts",
  ["#lm_removeServants"] = "%from 移除了 %arg 名将领 %svts",
}

-- 设置现有将领
---@param player ServerPlayer @ 主公角色
---@param svts ServantStruct[] @ 将领群
---@param skillName? string @ 技能名
---@param pos? integer @ 置入位置
---@return bool
LM.setServants = function(player, svts, skillName, pos)
  local skill = LM.getLordSkill(player)
  if not skill or not svts then return end

  local data = { ---@type LMImportStruct
    who = player,
    svts = svts,
    skillName = skillName,
    pos = pos,
  }
  local event = GameEvent[LM.LMImportServants]:create(data)
  -- local event = GameEvent:create(GameEvent[LM.LMImportServants], data)
  local _, ret = event:exec()
  return ret
end

-- 获得将领上限
---@param player Player @ 主公角色
---@return number @ 将领上限
LM.getServantsMax = function(player)
  assert(LM.getLordSkill(player))

  local value = LM.getServantsMark(player)
  ---@type LM.MaxServantsSkill[]
  local status_skills = Fk:currentRoom().status_skills[LM.MaxServantsSkill] or Util.DummyTable
  local max_fixed, correct = nil, 0
  for _, skill in ipairs(status_skills) do
    local f = skill:getFixed(player)
    if f ~= nil then
      max_fixed = f
      break
    end
    correct = correct + (skill:getCorrect(player) or 0)
  end

  return max_fixed or (value.max + math.max(correct, 0))
end

-- 设置初始将领上限
---@param player ServerPlayer @ 主公角色
---@param num number @ 将领上限
LM.setServantsMax = function(player, num)
  local skill = LM.getLordSkill(player)
  if not skill then return end

  local getsvts_mark = "@[lm_servants]" .. skill.name
  local value = LM.getServantsMark(player)
  value.max = num
  player.room:setPlayerMark(player, getsvts_mark, value)
end

-- 获得将领空位
---@param player Player 主公角色
---@return integer
LM.getServantSlot = function(player)
  return math.max(LM.getServantsMax(player) - LM.getServantsOccupied(player), 0)
end

-- 获得将领占用额
---@param player Player 主公角色
---@return integer
LM.getServantsOccupied = function(player)
  assert(LM.getLordSkill(player))

  local svts = LM.getServants(player)
  local getsvts = 0
  for _, svt in ipairs(svts) do
    local size = LM.getServantSize(player, svt)
    if size ~= nil then
      getsvts = getsvts + size
    end
  end
  return getsvts
end

-- 是否可以发现新的将领
---@param player Player 主公角色
---@return boolean
LM.canFindNewSvt = function(player)
  return LM.getServantSlot(player) > 0
end

-- 给主公变将（保留其军力技）
---@param player ServerPlayer @ 主公角色
---@param curlord string @ 现主公将
---@param full? boolean @ 是否血量满状态变身
---@param sendLog? boolean @ 是否发Log
---@param maxHpChange? boolean @ 是否改变体力上限，默认改变
---@param kingdomChange? boolean @ 是否改变势力，默认改变
LM.changeLord = function(player, curlord, full, sendLog, maxHpChange, kingdomChange)
  local skill = LM.getLordSkill(player)
  if not skill then return player.room:changeHero(player, curlord, full, false, sendLog, maxHpChange, kingdomChange) end
  local room = player.room

  room:handleAddLoseSkills(player, "-" .. skill.name, nil, false, true)

  player.room:changeHero(player, curlord, full, false, sendLog, maxHpChange, kingdomChange)

  if not LM.getLordSkill(player) then
    room:handleAddLoseSkills(player, skill.name, nil, false, true)
  end
end

-- 返回可以移除的所有将领
---@param player Player @ 主公角色
---@param filter? string[] @ 不考虑移除的将领名
---@param remover? Player @ 执行人
---@param noback? bool @ 是否不返回将领池
---@return ServantStruct[]
LM.getRemoveableSvts = function(player, filter, remover, noback)
  local skill = LM.getLordSkill(player)
  if not skill then return {} end
  local alls = LM.getServants(player)
  local getsvts = {}
  for _, svt in ipairs(alls) do
    if not table.contains(filter or {}, svt.name) then
      --- 《此将领不可被移除》
      if LM.canRemoveServant(player, svt, remover, noback) then
        table.insert(getsvts, svt)
      end
    end
  end
  return getsvts
end

--处理获得/失去将领技能
---@param player ServerPlayer @ 主公角色
---@param svts ServantStruct[] @ 将领数组
---@param way? integer @ GetSvtSkill/LoseSvtSkill
LM.handleSvtSkills = function(player, svts,way)
  assert(LM.getLordSkill(player))

  local room = player.room
  local skills = {}
  for _, svt in ipairs(svts) do
    table.insertTableIfNeed(skills, LM.getServantSkills(svt))
  end
  if #skills == 0 then return end

  local handlestr = way == LM.GetSvtSkill and "" or "-"
  local retstr = ""
  if #skills > 1 then
    retstr = handlestr + table.concat(skills, "|" .. handlestr)
  else
    retstr = handlestr .. skills[1]
  end

  if retstr then
    room:handleAddLoseSkills(player, retstr, nil, false)
  end
end

-- 把一名将领转移到给另一个玩家将池或者将领里(例如：李肃)
---@param from ServerPlayer @ 主公角色
---@param to ServerPlayer @ 主公角色
---@param changesvt string @ 将领
---@param toservant? bool @ 是否塞到将领里
---@param skillName? string @ 技能名
LM.changeSevtToTarget = function(from,to,changesvt, toservant, skillName)
  local skillf = LM.getLordSkill(from)
  local skillt = LM.getLordSkill(to)
  if not (skillf and skillt) then return end
  if toservant and not LM.canFindNewSvt(to) then return end

  local remove = LM.removeServants(from, {changesvt}, from, true, skillName)
  if #(remove or {}) ~= 0 then
    if toservant then
      LM.findServants(to, remove, skillName)
    else
      LM.setServantsPool(to, remove)
    end
  end
end

-- 移除指定将领
---@param player ServerPlayer @ 主公角色
---@param svts string[] @ 将领名数组
---@param proposer? ServerPlayer @移除者
---@param noback? bool @ 是否不返回将领池
---@param skillName? string @ 技能名
---@return ServantStruct[]? @ 被移除的将领
LM.removeServants = function(player, svts, proposer, noback, skillName)
  if #(svts or {}) == 0 then return end
  assert(type(svts[1]) == "string")
  local skill = LM.getLordSkill(player)
  if not skill then return end

  local data = { ---@type LMRemoveStruct
    who = player,
    svtnames = svts,
    proposer = proposer,
    skillName = skillName,
    noback = noback,
  }
  local event = GameEvent[LM.LMRemoveServants]:create(data)
  -- local event = GameEvent:new(LM.LMRemoveServants, data)
  local _, ret = event:exec()
  return data.svts
end

-- 选择并移除n个已有将领
---@param player ServerPlayer @ 主公角色
---@param n integer @ 移除数量
---@param except? string[] @ 排除的将领名列表
---@param controller? ServerPlayer @ 选择者
---@param noback? bool @ 是否不返回将领池
---@param skillName? string @ 技能名
---@return ServantStruct[] @ 被移除的将领
LM.removeServantsByChc = function(player, n, except, controller, noback, skillName)
  local skill = LM.getLordSkill(player)
  if not skill then return end

  local room = player.room
  local getsvts = LM.getServants(player)
  local filter = {}
  local default = {}
  for _, svt in ipairs(getsvts) do
    if not table.contains(except or {}, svt.name) and LM.canRemoveServant(player, svt, controller, noback) then
      table.insert(default, svt)
    else
      table.insert(filter, svt.name)
    end
  end
  if #default == 0 then return {} end

  local retcards
  if n < #default then
    local result = room:askForCustomDialog(controller or player, skillName or "#lm_removeservants",
    "packages/luming/qml/LM_ChooseGeneralsAndChoiceBox.qml", {
      getsvts,
      {"OK"},
      "#lm_removeservants",
      {},
      n,
      math.min(n,#getsvts),
      filter,
      player.id, --- 传达可知信息
    })
    if result == "" then return end
    local reply = json.decode(result)
    if reply.choice ~= "OK" then return end
    retcards = reply.cards
  else
    retcards = default
  end

  return LM.removeServants(player, table.map(retcards, Util.NameMapper), controller, noback, skillName)

end

-- 获得指定将领
---@param player ServerPlayer @ 主公角色
---@param svts string[] | ServantStruct[] @ 将领名数组
---@param skillName? string @ 技能名
---@return ServantStruct[]? @ 发现的将领
LM.findServants = function(player,svts, skillName)
  if #svts == 0 then return {} end
  local skill = LM.getLordSkill(player)
  if not skill then return end

  local room = player.room
  -- local svts_mark = "&"..player.general.."_svts"
  local getsvts = LM.getServantsPool(player)
  -- if #getsvts == 0 then return end

  -- 时机：索引将领时
  player.room.logic:trigger("lm.indexingServants", player, svts, false)

  local actualsvts = {}
  if type(svts[1]) == "string" then
    -- 开始从将领池内索引将领
    for _,st in ipairs(svts) do
      local svt = table.find(getsvts, function(s) return s.name == st end)
      if svt then
        table.insert(actualsvts, svt)
      end
    end

    if #actualsvts ~= 0 then
      -- 要求的将领存在，则不考虑缺失武将
      for _, svt in ipairs(actualsvts) do
        table.removeOne(getsvts, svt)
      end
      room:setPlayerMark(player, "@[lm_servants]&" .. skill.name, getsvts)
    else
      -- 要求的将领不存在，考虑直接导入武将
      for _,st in ipairs(svts) do
        table.insert(actualsvts, LM.initServant(player, st))
      end
    end
  else
    -- 直接给的是将领，那就不用找了（@发现）
    actualsvts = svts
  end

  return LM.setServants(player, actualsvts, skillName) and actualsvts
end

-- 发现将领
---@param player ServerPlayer @ 主公角色
---@param min? number @ 最小值（默认1）
---@param max? number @ 最大值（默认1）
---@param seln? number @ 可选将领数（默认3）
---@param skillName? string @ 技能名
---@param prompt? string @ 询问信息
---@param filter? fun(svt:ServantStruct):bool @ 用以随机抽取的过滤函数
---@param skip_find? boolean @ 是否不直接获得？
---@return ServantStruct[] @ 发现的将领
LM.findServantsByChc = function(player, min, max, seln, skillName, prompt, filter, skip_find)
  local skill = LM.getLordSkill(player)
  if not skill then return {} end
  if not LM.canFindNewSvt(player) then return {} end

  local room = player.room
  local svts = LM.getServantsPool(player)
  if #svts == 0 then return {} end

  if type(filter) ~= "function" then filter = Util.TrueFunc end
  local data = {
    all_num = math.min(seln or 3,#svts),
    min = min or 1,
    max = max or 1,
  }

  -- 时机：发现将领前
  player.room.logic:trigger("lm.findingServant", player, data, false)

  min = data.min
  max = data.max
  local disabled = {}

  if #(data.svts or {}) == 0 then
    if ((data.all_num or 0) < 1) then return {} end
    data.svts = {}
    local remain = {}
    local is_enough = true
    for _, svt in ipairs(svts) do
      if is_enough and filter(svt) then
        table.insert(data.svts, svt)
        if #data.svts == seln then is_enough = false end
        ---@type LM.ServantSkill[]
        local ServantSkill = room.status_skills[LM.ServantSkill] or Util.DummyTable
        local prohibit = nil
        for _, svtskill in ipairs(ServantSkill) do
          if svtskill:prohibitFind(player, svt) then
            prohibit = true
            break
          end
        end
        if prohibit then
          table.insert(disabled, svt.name)
        end
      else
        table.insert(remain, svt)
      end
    end
    room:setPlayerMark(player, "@[lm_servants]&" .. skill.name, remain)
  end

  local default = table.filter(data.svts, function(svt) return not table.contains(disabled, svt.name) end)

  if #default ~= 0 then
    local result = room:askForCustomDialog(player, skillName or "#lm_findservants",
    "packages/luming/qml/LM_ChooseGeneralsAndChoiceBox.qml", {
      data.svts,
      {"OK"},
      prompt or "#lm_findservants",
      {},
      min,
      max,
      disabled,
      player.id
    })
    if result ~= "" then
      local reply = json.decode(result)
      if reply.choice ~= "OK" then return {} end
      data.result = reply.cards
    else
      data.result = table.random(default, min)
    end

    -- 时机：发现将领时
    player.room.logic:trigger("lm.findServant", player, data, false)

    if not skip_find then
      LM.findServants(player, data.result, skillName)
    end
  end

  LM.setServantsPool(player, table.filter(data.svts, function(svt)
    return not table.find(data.result or {}, function(s) return s.name == svt.name end)
  end))

  return data.result or {}
end

-- 选择几个将领
---@param player ServerPlayer @ 主公
---@param n number @ 最小值
---@param m number @ 最大值
---@param filter? fun(element, index, array): any @ 过滤
---@param skillName? string @ 技能名
---@param prompt? string @ 询问信息
---@return ServantStruct[] @ 选择将领
LM.selectServant = function(player, n, m, filter, skillName, prompt)
  local skill = LM.getLordSkill(player)
  if not skill then return {} end
  local getsvts = LM.getServants(player)
  if #getsvts == 0 then return {} end
  if not n then n = 1 end
  if not m then m = #getsvts end
  if not skillName then skillName = "lm_selectservants" end
  if not prompt then prompt = "#lm_selectservants:::" .. n .. ":" .. m end
  local disabled = {}
  local default = table.simpleClone(getsvts)
  if type(filter) == "function" then
    for i, svt in ipairs(getsvts) do
      if not filter(svt, i, getsvts) then
        table.insert(disabled, svt.name)
      end
    end
    default = table.filter(getsvts, filter)
  end

  local retcards
  if n < #default then
    local result = player.room:askForCustomDialog(player, skillName,
    "packages/luming/qml/LM_ChooseGeneralsAndChoiceBox.qml", {
      getsvts,
      {"OK"},
      prompt,
      disabled,
      n,
      m,
      disabled,
      player.id
    })
    if result == "" then return {} end
    local reply = json.decode(result)
    if reply.choice ~= "OK" then return {} end
    retcards = reply.cards
  else
    retcards = default
  end
  return retcards
end

-- 判断一个将领是否可以翻面(雾)
---@param servant string | ServantStruct @ 将领(或将领名)
---@return bool
LM.canFlipServant = function(servant)
  if type(servant) == "table" then servant = servant.name end
  local actual = Fk.generals[servant]
  if not actual then return end
  local another
  if servant:endsWith("_A") then --- TODO 如果有更多态的玩意的话还得改
    another = servant:gsub("_A", "_B")
  elseif servant:endsWith("_B") then
    another = servant:gsub("_B", "_A")
  else return false
  end
  if Fk.generals[another] then return true end
  return false
end

-- 给将领翻面
---@param player ServerPlayer @ 主公
---@param servant string | ServantStruct @ 将领
---@return ServantStruct? @ 翻面后的将领
LM.flipServant = function(player, servant)
  if type(servant) == "table" then servant = servant.name end
  local skill = LM.getLordSkill(player)
  if not (skill and LM.canFlipServant(servant)) then return end

  local room = player.room
  local svts = LM.getServantsMark(player)
  local pools = LM.getServantsPool(player)
  local not_pool = true
  local acturalsvt = table.find(svts.svts, function(s) return s.name == servant end)
  if not acturalsvt then
    not_pool = false
    acturalsvt = table.find(pools, function(s) return s.name == servant end)
  end
  if not acturalsvt then return end
  local another
  if servant:endsWith("_A") then --- TODO 如果有更多态的玩意的话还得改
    another = servant:gsub("_A", "_B")
  else
    another = servant:gsub("_B", "_A")
  end
  if not_pool then
    LM.handleSvtSkills(player, {acturalsvt}, LM.LoseSvtSkill)
    acturalsvt.name = another
    room:setPlayerMark(player, "@[lm_servants]" .. skill.name, svts)
    LM.handleSvtSkills(player, {acturalsvt}, LM.GetSvtSkill)
  else
    -- local pos = table.indexOf(pools, servant)
    -- table.remove(pools, pos)
    -- table.insert(pools, pos, another)
    acturalsvt.name = another
    room:setPlayerMark(player, "@[lm_servants]&" .. skill.name, pools)
  end
  return acturalsvt
end
Fk:loadTranslationTable{
  ["lm_selectservants"] = "选择将领",
  ["#lm_selectservants"] = "选择将领：请选择%arg-%arg2名将领",
}

-- 自定义技能

---ServantSkill 将领技
---@class LM.ServantSkill: StatusSkill
LM.ServantSkill = StatusSkill:subclass("LM.ServantSkill")

-- 禁止发现此将领
---@param player ServerPlayer @ 主公
---@param svt ServantStruct @ 将领
---@return boolean
function LM.ServantSkill:prohibitFind(player, svt)
  return false
end

-- 禁止移除此将领
---@param player ServerPlayer @ 主公
---@param svt ServantStruct @ 将领
---@param proposer? ServerPlayer @ 执行者
---@param noback? bool @ 是否为移出游戏
---@return bool
function LM.ServantSkill:prohibitRemove(player, svt, proposer, noback)
  return nil
end

-- 此将领占用X格将领上限（固定最终值）
---@param player ServerPlayer @ 主公
---@param svt ServantStruct @ 将领
---@return integer?
function LM.ServantSkill:fixedSize(player, svt)
  return nil
end

---此将领占用将领上限+X（修正值）
---@param player ServerPlayer @ 主公
---@param svt ServantStruct @ 将领
---@return integer
function LM.ServantSkill:correctSize(player, svt)
  return 0
end

-- 此将领不计入将领上限
---@param player ServerPlayer @ 主公
---@param svt ServantStruct @ 将领
---@return bool
function LM.ServantSkill:excludeFrom(player, svt)
  return nil
end

---@class LM.ServantSkillSpec: StatusSkillSpec
-- 禁止发现此将领
---@field public prohibit_find? fun(self: LM.ServantSkill, player: ServerPlayer, svt: ServantStruct): bool
-- 禁止移除此将领
---@field public prohibit_remove? fun(self: LM.ServantSkill, player: ServerPlayer, svt: ServantStruct, proposer?: ServerPlayer, noback?: bool): bool
-- 此将领将领上限+X（修正值）
---@field public correct_size? fun(self: LM.ServantSkill, player: ServerPlayer, svt: ServantStruct): integer
-- 此将领占用X格将领上限（固定最终值）
---@field public fixed_size? fun(self: LM.ServantSkill, player: ServerPlayer, svt: ServantStruct): integer?
-- 此将领不计入将领上限
---@field public exclude_from? fun(self: LM.ServantSkill, player: ServerPlayer, svt: ServantStruct): bool

---将领技定义式
---@param spec LM.ServantSkillSpec
---@return LM.ServantSkill
function LM.CreateServantSkill(spec)
  assert(type(spec.name) == "string")
  assert(type(spec.prohibit_find) == "function" or type(spec.prohibit_remove) == "function" or
  type(spec.correct_size) == "function" or type(spec.fixed_size) == "function" or
  type(spec.exclude_from) == "function")

  local skill = LM.ServantSkill:new(spec.name)
  fk.readStatusSpecToSkill(skill, spec)

  if type(spec.prohibit_find) == "function" then
    skill.prohibitFind = spec.prohibit_find
  end
  if type(spec.prohibit_remove) == "function" then
    skill.prohibitRemove = spec.prohibit_remove
  end
  if type(spec.correct_size) == "function" then
    skill.correctSize = spec.correct_size
  end
  if type(spec.fixed_size) == "function" then
    skill.fixedSize = spec.fixed_size
  end
  if type(spec.exclude_from) == "function" then
    skill.excludeFrom = spec.exclude_from
  end

  return skill
end

---MaxServantsSkill 将领上限技
---@class LM.MaxServantsSkill: StatusSkill
LM.MaxServantsSkill = StatusSkill:subclass("LM.MaxServantsSkill")

-- 将领上限最终值
---@param player ServerPlayer @ 主公
---@return integer?
function LM.MaxServantsSkill:getFixed(player)
  return nil
end

-- 将领上限修正值
---@param player ServerPlayer @ 主公
---@return integer
function LM.MaxServantsSkill:getCorrect(player)
  return 0
end

---@class LM.MaxServantsSkillSpec: StatusSkillSpec
-- 将领上限最终值
---@field public fixed_func? fun(self: LM.MaxServantsSkill, player: ServerPlayer): integer?
-- 将领上限修正值
---@field public correct_func? fun(self: LM.MaxServantsSkill, player: ServerPlayer): integer

---将领上限技定义式
---@param spec LM.MaxServantsSkillSpec
---@return LM.MaxServantsSkill
function LM.CreateMaxServantsSkill(spec)
  assert(type(spec.name) == "string")
  assert(type(spec.correct_func) == "function" or type(spec.fixed_func) == "function")

  local skill = LM.MaxServantsSkill:new(spec.name)
  fk.readStatusSpecToSkill(skill, spec)

  if type(spec.correct_func) == "function" then
    skill.getCorrect = spec.correct_func
  end
  if type(spec.fixed_func) == "function" then
    skill.getFixed = spec.fixed_func
  end

  return skill
end

return LM